﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using GeneticAlgorythm.Algorythm;
using Cudafy.Host;
using Cudafy.Maths.BLAS;
using Cudafy.Types;
using Cudafy;
using System.Diagnostics;

namespace GeneticAlgorythm.Problem
{
    public class ObjectiveFunctionCalculatorGPU : ObjectiveFunctionValueCalculatotBase
    {
        private float[,] _preparedCostsMatrix;
        private float[,] _cargoQuantityMatrix;
        private GPGPU _gpu;
        private GPGPUBLAS _gpublas;
        float[,] _costsMatrixOnGpu = null;
        float[,] _cargoQuantityMatrixOnGpu = null;
        private long[] _timerValues = new long[2];
        private Stopwatch _timer = new Stopwatch();

        public ObjectiveFunctionCalculatorGPU()
        {
            _gpu = initializeGPU();
            _gpublas = GPGPUBLAS.Create(_gpu);
        }
        ~ObjectiveFunctionCalculatorGPU()
        {
            _gpublas.Dispose();
            _gpu.Free(_costsMatrixOnGpu);
            _gpu.Free(_cargoQuantityMatrixOnGpu);
        }
        public override float calculate(Chromosome chromosome, float[,] cargoQuantityMatrix, float[,] costsMatrix)
        {
            int size = chromosome.getSize();
            float[] cast2 = null;
            if (_cargoQuantityMatrix == null)
            {
                _cargoQuantityMatrix = cargoQuantityMatrix;
                _costsMatrixOnGpu = _gpu.Allocate<float>(costsMatrix);
                _cargoQuantityMatrixOnGpu = _gpu.Allocate<float>(_cargoQuantityMatrix);
                _gpu.CopyToDevice(_cargoQuantityMatrix, _cargoQuantityMatrixOnGpu);
                cast2 = _gpu.Cast(_cargoQuantityMatrixOnGpu, size * size);
            }
            _preparedCostsMatrix = prepareCostsMatrix(costsMatrix, chromosome);

            _gpu.CopyToDevice(_preparedCostsMatrix, _costsMatrixOnGpu);
            float[] cast = _gpu.Cast(_costsMatrixOnGpu, size * size);

            return _gpublas.DOT(cast, cast2, size * size, 0, 1, 0, 1);
        }

        public override float[] calculate(List<Chromosome> chromosomes, float[,] cargoQuantityMatrix, float[,] costsMatrix)
        {
            _timer.Restart();
            int chromosomeSize = chromosomes[0].getSize();
            int numberOfChromosomes = chromosomes.Count;

            float[] cargoQuantityVector = prepareCargoQuantityVector(cargoQuantityMatrix);
            float[,] preparedCostsMatrices = prepareCostsMatrices(chromosomes, costsMatrix);

            _timer.Stop();
            _timerValues[0] = _timer.ElapsedMilliseconds;
            _timer.Restart();

            float[] cargoQuantityVectorOnGpu = _gpu.Allocate<float>(cargoQuantityVector);
            float[,] preparedCostsMatricesOnGpu = _gpu.Allocate<float>(preparedCostsMatrices);

            _gpu.CopyToDevice(cargoQuantityVector, cargoQuantityVectorOnGpu);
            _gpu.CopyToDevice(preparedCostsMatrices, preparedCostsMatricesOnGpu);

            float[] preparedCostsMatricesOnGpuCasted = _gpu.Cast(preparedCostsMatricesOnGpu, chromosomeSize * chromosomeSize * numberOfChromosomes);
            float[] resultOnGpu = _gpu.Allocate<float>(numberOfChromosomes);
            float[] result = new float[numberOfChromosomes];


            _gpublas.GEMV(numberOfChromosomes, chromosomeSize * chromosomeSize, 1, preparedCostsMatricesOnGpuCasted, cargoQuantityVectorOnGpu, 0, resultOnGpu);
            _gpu.CopyFromDevice(resultOnGpu, result);

            _gpu.Free(preparedCostsMatricesOnGpu);
            _gpu.Free(cargoQuantityVectorOnGpu);
            _gpu.Free(resultOnGpu);

            _timer.Stop();
            _timerValues[1] = _timer.ElapsedMilliseconds;
            _timer.Restart();

            return result;
        }

        public override long[] getTimerValues()
        {
            return _timerValues;
        }

        private GPGPU initializeGPU()
        {
            CudafyModes.Target = eGPUType.Cuda;
            return _gpu = CudafyHost.CreateDevice(CudafyModes.Target);
        }

        private float[,] prepareCostsMatrix(float[,] costsMatrix, Chromosome chromosome)
        {
            int size = chromosome.getSize();
            float[,] newMatrix = new float[size, size];

            for (int i = 0; i < size; i++)
            {
                for (int j = 0; j < size; j++)
                {
                    if (i == j) continue;
                    newMatrix[i, j] = costsMatrix[(int)chromosome.getGene(i) - 1, (int)chromosome.getGene(j) - 1];
                }

            }
            return newMatrix;
        }

    }
}
